import Dep from "./observe/dep";
import { observe } from "./observe/index";
import Wacther, { nextTick } from "./observe/watcher";

export function initState(vm) {
    const options = vm.$options;

    if (options.data) {
        initData(vm);
    }

    if (options.computed) {
        initComputed(vm);
    }
    if(options.watch){
        initWatch(vm);
    }
}

function initWatch(vm){
    let watch = vm.$options.watch;

    for(let key in watch){
        const handler = watch[key];
        //字符串 数组 函数 对象写法暂时不考虑了
        if(Array.isArray(handler)){
            for(let i=0;i<handler.length;i++){
                createWatcher(vm,key,handler[i]);
            }
        }else{
            createWatcher(vm,key,handler)
        }
    }
}

function createWatcher(vm,key,handler){
    //字符串 函数
    if(typeof handler === 'string'){
        handler = vm[handler];
    }
    return vm.$watch(key,handler)
}

function proxy(vm, source, key) {
    Object.defineProperty(vm, key, {
        get() {
            return vm[source][key]
        },
        set(newValue) {
            vm[source][key] = newValue;
        }
    })

}

function initData(vm) {
    let data = vm.$options.data;

    //data可以是函数也可以是对象
    //如果是函数就拿到函数的返回值 否则就直接采用data作为数据源
    data = vm._data = typeof data === 'function' ? data.call(vm) : data;

    //属性劫持 采用defineProperty将所有的属性进行数据劫持

    //我期望用户可以直接通过vm.xxx获取值 也可以取值vm._data.xxx
    for (let key in data) {
        proxy(vm, '_data', key);
    }

    observe(data);
}

function initComputed(vm) {
    const computed = vm.$options.computed;
    const watchers = vm._computedWatchers = {};//将计算属性wathcer保存在vm上
    for (let key in computed) {
        let userDef = computed[key];

        //我们需要监控 计算属性中get的变化
        let fn = typeof userDef === 'function' ? userDef : userDef.get;
        //如果直接new Watcher 默认就会执行fn
        //将属性和watcher对应起来
        watchers[key] = new Wacther(vm, fn, { lazy: true })

        defineComputed(vm, key, userDef);
    }
}

function defineComputed(target, key, userDef) {
    // const getter = typeof userDef === 'function' ? userDef : userDef.get;
    const setter = userDef.set || (() => { });
    //可以通过实例拿到对应的属性
    Object.defineProperty(target, key, {
        get: createComputedGetter(key),
        set: setter
    })
}

//计算属性根本不会收集依赖，只会让自己的依赖属性去收集依赖
function createComputedGetter(key){
    //我们需要检测是否要执行这个getter
    return function(){
        const watcher = this._computedWatchers[key]; //this -> vm
        if(watcher.dirty){
            //如果是脏的就去执行 用户传入的函数
            watcher.evaluate();//求值后 dirty变成了false 下次就不求值了
        }
        if(Dep.target){
            //计算属性出栈后 还要渲染watcher 应该让计算属性watcher里面的属性也去收集上一层wathcer
            watcher.depend();
        }
        return watcher.value;//最后返回的是wathcer上的值
    }
}

export function initStateMixin(Vue){
    Vue.prototype.$nextTick = nextTick;
    //最终调用的都是这个方法
Vue.prototype.$watch = function(exprOrFn,cb){
    //firstname的值变化了 直接执行cb函数即可
    new Wacther(this,exprOrFn,{user:true},cb);
}
}